﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using WMM.Tapioca.Taxonomy;
using WMM.Tapioca.Linkbase;
using System.Xml.Schema;
using System.Xml.Linq;
using System.Xml;
using WMM.Tapioca.Utilities;

namespace WMM.Tapioca.XbrlDimension
{
    /// <summary>
    /// An Dimension class.
    /// </summary>
    public class Dimension : ICloneable
    {
        private string dimensionType;

        /// <returns> Dimension Type. </returns>
        public string DimensionType
        {
            get { return dimensionType; }
            set { dimensionType = value; }
        }

        private Concept concept;

        /// <summary>
        /// Concept.
        /// </summary>
        public Concept Concept
        {
            get { return concept; }
            set { concept = value; }
        }

        private List<Concept> domains;

        /// <summary>
        /// Domain Member.
        /// </summary>
        public List<Concept> Domains
        {
            get { return domains; }
            set { domains = value; }
        }

        private List<Concept> members;

        /// <summary>
        /// Domain Member.
        /// </summary>
        public List<Concept> Members
        {
            get { return members; }
            set { members = value; }
        }

        private List<KeyValuePair<string, Concept>> domainMemberSet;

        /// <summary>
        /// Domain Member Set.
        /// </summary>
        public List<KeyValuePair<string, Concept>> DomainMemberSet
        {
            get { return domainMemberSet; }
            set { domainMemberSet = value; }
        }

        private Concept defaultConcept;

        /// <summary>
        /// Default Dimensions.
        /// </summary>
        public Concept DefaultConcept
        {
            get { return defaultConcept; }
            set { defaultConcept = value; }
        }

        private string defaultDimensionRole;

        public string DefaultDimensionRole
        {
            get { return defaultDimensionRole; }
            set { defaultDimensionRole = value; }
        }

        private Arc defaultDimensionArc;

        public Arc DefaultDimensionArc
        {
            get { return defaultDimensionArc; }
            set { defaultDimensionArc = value; }
        }

        private bool typed;

        /// <summary>
        /// Typed.
        /// </summary>
        public bool Typed
        {
            get { return typed; }
            set { typed = value; }
        }

        private XmlSchemaElement typedElement;

        /// <summary>
        /// Typed Element.
        /// </summary>
        public XmlSchemaElement TypedElement
        {
            get { return typedElement; }
            set { typedElement = value; }
        }                

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="concept">
        ///            The XBRL concept representing this dimension. </param>
        public Dimension(Concept concept)
        {
            this.concept = concept;
            domains = new List<Concept>();
            domainMemberSet = new List<KeyValuePair<string, Concept>>();
            Members = new List<Concept>();
            typed = false;
            typedElement = null;

            dimensionType = GetDimensionType(concept);
        }


        private string GetDimensionType(Concept concept)
        {
            dimensionType = "explicitDimension";

            foreach (XmlAttribute currentAttribute in concept.SchemaElement.UnhandledAttributes)
            {
                if (currentAttribute.Name.Equals("xbrldt:typedDomainRef"))
                {
                    dimensionType = "typedDimension";
                }
            }

            return dimensionType;
        }

        /// <summary>
        ///  Every Dimension must have a dimension QName.
        /// </summary>
        public XName GetName()
        {
            XNamespace ns = concept.SchemaElement.QualifiedName.Namespace;
            XName nameQN = ns + concept.Name;
            return nameQN;
        }

        /// <summary>
        /// TODO: Bad practice: super.clone() is not invoked here. This mehtod clones
        /// the current object and returns the clone.
        /// </summary>
        /// <returns> A clone of the current Dimension object. </returns>
        public virtual object Clone()
        {
            //Dimension d = new Dimension((Concept)concept.clone());
            //d.DomainMemberSet = (Set)((HashSet)domainMemberSet).clone();
            //d.Typed = typed;
            //if (typedElement != null)
            //{
            //    d.TypedElement = (Element)typedElement.clone();
            //}
            //return d;
            return this.MemberwiseClone();
        }

        /// <summary>
        /// Indicates whether this dimension contains a certain domain member. It can
        /// be specified whether the domain member must have the xbrldt:usable
        /// attribute set to "true" (or not set at all) or not.
        /// </summary>
        /// <param name="domainMember">
        ///            Concept representing the certain domain member </param>
        /// <param name="usable">
        ///            Indicates whether the xbrldt:usable attribute shall be taken
        ///            into account. True if yes, false if no. </param>
        /// <returns> If usable is set to true, the method returns true if and only if
        ///         the domain member is part of that dimension and has no
        ///         xbrldt:usable attribute set to false (or no xbrldt:usable
        ///         attribute at all). If usable is set to false, the method also
        ///         returns true if the domain member has an xbrldt:usable attribute
        ///         set to false. </returns>
        public virtual bool containsDomainMember(Concept domainMember, bool usable)
        {
            IEnumerator domainMemberListIterator = domainMemberSet.GetEnumerator();
            while (domainMemberListIterator.MoveNext())
            {
                ExtendedLinkElement currXLinkElement = (ExtendedLinkElement)domainMemberListIterator.Current;
                if (currXLinkElement.IsLocator())
                {
                    Locator currLoc = (Locator)currXLinkElement;
                    //if (currLoc.Concept.Equals(domainMember))
                    //{
                    //    return usable ? currLoc.Usable : true;
                    //}
                }
            }
            return false;
        }

        /// <summary>
        /// This method tests for "equality" between two Dimension objects. They are
        /// equal if:<br/> - both dimensions are either explicit nor typed<br/> -
        /// the concept which represents the dimensions are equal<br/> - the domain
        /// member set of both Dimension objects are equal<br/> - if both Dimension
        /// objects are representing typed dimensions, the schema element describing
        /// these dimensions must be equal
        /// </summary>
        /// <returns> True if both objects are equal, otherwise false. </returns>
        public override bool Equals(object obj)
        {
            if (this == obj)
            {
                return true;
            }
            if (!(obj is Dimension))
            {
                return false;
            }
            Dimension otherDim = (Dimension)obj;

            return typed == otherDim.typed && concept == otherDim.concept && domainMemberSet.Equals(otherDim.domainMemberSet) && (typedElement == null ? otherDim.typedElement == null : typedElement.Equals(otherDim.typedElement));
        }

        /// <summary>
        /// Calculates a hash code for the dimension.
        /// </summary>
        /// <returns>
        /// A hash code for the dimension.
        /// </returns>
        public override int GetHashCode()
        {
            return this.concept.GetHashCode();
        }
    }
}
